home *** CD-ROM | disk | FTP | other *** search
Text File | 2007-10-12 | 66.3 KB | 2,232 lines |
- //
- // BEGIN FLOCK GPL
- //
- // Copyright Flock Inc. 2005-2007
- // http://flock.com
- //
- // This file may be used under the terms of of the
- // GNU General Public License Version 2 or later (the "GPL"),
- // http://www.gnu.org/licenses/gpl.html
- //
- // Software distributed under the License is distributed on an "AS IS" basis,
- // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- // for the specific language governing rights and limitations under the
- // License.
- //
- // END FLOCK GPL
- //
-
- const FEED_CONTRACTID = '@flock.com/feed;1';
- const FEED_CLASSID = Components.ID('2f7499f4-b47f-4350-a6c5-aee66520c720');
- const FEED_CLASSNAME = 'Flock Feed';
-
- const ITEM_CONTRACTID = '@flock.com/feed-item;1';
- const ITEM_CLASSID = Components.ID('f97d9a24-2463-4e8d-b383-9e41f2bc60e8');
- const ITEM_CLASSNAME = 'Flock Feed Item';
-
- const FC_CONTRACTID = '@flock.com/feed-context;1';
- const FC_CLASSID = Components.ID('6e95c222-6707-42cf-aa1b-12baea3983e6');
- const FC_CLASSNAME = 'Flock Feed Context';
-
- const FF_CONTRACTID = '@flock.com/feed-folder;1';
- const FF_CLASSID = Components.ID('16b7ab70-c745-4f53-83a0-3375a031a113');
- const FF_CLASSNAME = 'Flock Feed Folder';
-
- const FM_CONTRACTID = '@flock.com/feed-manager;1';
- const FM_CLASSID = Components.ID('287848be-bbc9-42aa-953e-2ec916eb8d49');
- const FM_CLASSNAME = 'Flock Feed Manager';
-
-
- const FEED_CONTENT_FILE = 'feedcontent.sqlite';
-
- const EXCERPT_MAX_WORDS = 50;
- const TITLE_TRIM_MAX_CHARS = 30;
-
- const PURGE_RUN_INTERVAL = 500;
- const PURGE_SLEEP_INTERVAL = 1500;
-
- const UNKNOWN_FEED_TYPE = 'unknown';
-
- const URI_FEED_PROPERTIES = 'chrome://flock/locale/feeds/feeds.properties';
-
- const URN_FEED_ROOT = 'urn:flock:feedroot';
-
- const NEWS_CONTEXT_NAME = 'news';
- const LIVEMARKS_CONTEXT_NAME = 'livemarks';
-
- const ATTR_URI_LIST = ['action', 'href', 'src', 'longdesc', 'usemap',
- 'cite'];
-
-
- /* prefs */
- const PREF_EXPIRATION_TIME_BRANCH = 'flock.feeds.expiration_time';
- const PREF_EXPIRATION_TIME = '.subscriptions';
- const PREF_METADATA_EXPIRATION_TIME = '.metadata_only';
-
- const DEFAULT_EXPIRATION_TIME = 60;
- const DEFAULT_METADATA_EXPIRATION_TIME = 24 * 60;
-
- const FAVICON_EXPIRATION_TIME = 24 * 60 * 60 * 1000;
-
-
- /* cardinal to danphe migration */
- const FLOCK_NS = 'http://flock.com/rdf#';
-
- const OLD_FEEDS_RDF_FILE = 'flock_subscriptions.rdf';
- const OLD_FEEDS_RDF_FILE_RELIC = 'flock_subscriptions_old.rdf';
- const SUBSCRIPTIONS_RDF_ROOT = 'urn:flock:feed:subscriptions';
-
- const OLD_FLAGGED_RDF_FILE = 'flock_feeds_flagged.rdf';
- const OLD_FLAGGED_RDF_FILE_RELIC = 'flock_feeds_flagged_old.rdf';
- const FLAGGED_RDF_ROOT = 'urn:flock:feed';
-
- const OLD_ROOT_RDF_FILE = 'flock_feeds_root.rdf';
- const OLD_DISCOVERY_RDF_FILE = 'flock_feeds_discovery.rdf';
-
- const OLD_FEED_DATA_DIR = 'feeds';
- const OLD_FEED_DATA_FILENAME = 'feed.rdf';
- const OLD_FEED_DATA_RDF_PREFIX = 'urn:flock:feed:';
- const OLD_FEED_DATA_POST_PREFIX = OLD_FEED_DATA_RDF_PREFIX + 'post:';
-
- const FORMAT_CONVERSIONS = { 'Atom 1.0': 'atom',
- 'Atom 0.3': 'atom03',
- 'RSS 2.0' : 'rss2',
- 'RSS 1.0' : 'rss1',
- 'RSS 0.9' : 'rss090',
- 'RSS 0.91': 'rss091',
- 'RSS 0.92': 'rss092',
- 'RSS 0.93': 'rss093',
- 'RSS 0.94': 'rss094',
- };
-
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- const Cr = Components.results;
-
- /* from nspr's prio.h */
- const PR_RDONLY = 0x01;
- const PR_WRONLY = 0x02;
- const PR_RDWR = 0x04;
- const PR_CREATE_FILE = 0x08;
- const PR_APPEND = 0x10;
- const PR_TRUNCATE = 0x20;
- const PR_SYNC = 0x40;
- const PR_EXCL = 0x80;
-
-
- var gIOService = null;
- var gFeedStorage = null;
- var gReadMoreBlurb = null;
-
-
- function getObserverService() {
- return Cc['@mozilla.org/observer-service;1']
- .getService(Ci.nsIObserverService);
- }
-
-
- function excerptText(text, link) {
- var words = text.split(/\s+/);
-
- if (words.length <= EXCERPT_MAX_WORDS)
- return text;
-
- var excerpt = words.slice(0, EXCERPT_MAX_WORDS + 1).join(' ');
-
- if (link)
- excerpt += '
<a href="' + link + '">' + gReadMoreBlurb + '</a>';
-
- return excerpt;
- }
-
-
- function checkEmpty(value) {
- if (value == null) return null;
- var str = value.toString();
- return str ? str : null;
- }
-
- function makeURI(uri) {
- try {
- return gIOService.newURI(uri, null, null);
- }
- catch (e) {
- return null;
- }
- }
-
-
- function getFeedNode(url, coop) {
- var urn = coop.Feed.get_id({ URL: url });
- return coop.get(urn);
- }
-
-
- function insertFavicon(feedNode) {
- var age = Date.now() - feedNode.lastFaviconFetch.getTime();
- if (age < FAVICON_EXPIRATION_TIME)
- return;
-
- var url = makeURI(feedNode.link);
- if (!url)
- return;
-
- if (!/^https?/.test(url.scheme))
- return;
-
- feedNode.lastFaviconFetch = new Date();
-
- var faviconURL = url.prePath + '/favicon.ico';
-
- var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
- .createInstance(Ci.nsIXMLHttpRequest);
-
- hr.onload = function(evt) {
- if (evt.target.status == 200)
- feedNode.favicon = faviconURL;
- };
-
- hr.detachLoadGroup = true;
- hr.open('HEAD', faviconURL);
- hr.send(null);
- }
-
- function getContextsForFeed(feedNode, coop) {
- var contexts = [];
- var parents = feedNode.getParents();
- for each (var parent in parents) {
- var node = parent;
- while (!node.isInstanceOf(coop.FeedContext)) {
- var nodeParents = node.getParents();
- if (nodeParents.length)
- node = nodeParents[0];
- else
- break;
- }
- if (node.isInstanceOf(coop.FeedContext))
- contexts.push(node);
- }
- return contexts;
- }
-
- function contextHasObject(contextName, obj, coop) {
- if (obj.isInstanceOf(coop.FeedContext))
- return obj.name == contextName;
-
- var parents = obj.getParents();
- for each (var parent in parents) {
- var node = parent;
- while (!node.isInstanceOf(coop.FeedContext)) {
- var nodeParents = node.getParents();
- if (nodeParents.length)
- node = nodeParents[0];
- else
- break;
- }
-
- if (node.isInstanceOf(coop.FeedContext) && node.name == contextName)
- return true;
- }
-
- return false;
- }
-
- function setFeedIndexable(feedNode, indexable) {
- feedNode.isIndexable = indexable;
-
- var items = feedNode.children.enumerate();
- while (items.hasMoreElements()) {
- var item = items.getNext();
- item.isIndexable = indexable;
- }
- }
-
- function updateNextRefresh(feedNode, coop) {
- var contexts = getContextsForFeed(feedNode, coop);
- if (contexts.length == 0)
- return;
-
- var refreshInterval = contexts[0].refreshInterval;
-
- if (contexts.length > 1) {
- for (var i = 1; i < contexts.length; i++) {
- refreshInterval = Math.min(refreshInterval, contexts[i].refreshInterval);
- }
- }
-
- feedNode.nextRefresh = new Date(Date.now() + refreshInterval * 1000);
- }
-
- function feedItemSorter(a, b) {
- var res = a.datevalue - b.datevalue;
- if (res == 0)
- return b.indexvalue - a.indexvalue;
- else
- return res;
- }
-
- function createStatement(dbconn, sql) {
- var stmt = dbconn.createStatement(sql);
- var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
- .createInstance(Ci.mozIStorageStatementWrapper);
-
- wrapper.initialize(stmt);
- return wrapper;
- }
-
- function FeedStorage() {
- var dbfile = Cc['@mozilla.org/file/directory_service;1']
- .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
- dbfile.append(FEED_CONTENT_FILE);
-
- var storageService = Cc['@mozilla.org/storage/service;1']
- .getService(Ci.mozIStorageService);
- this._DBConn = storageService.openDatabase(dbfile);
-
- var schema = 'id STRING PRIMARY KEY, feed STRING, ' +
- 'content STRING, excerpt STRING';
-
- try {
- this._DBConn.createTable('feed_content', schema);
- }
- catch (e) { }
-
- this._queryContent = createStatement(this._DBConn,
- 'SELECT content FROM feed_content WHERE id = :id');
- this._queryExcerpt = createStatement(this._DBConn,
- 'SELECT excerpt FROM feed_content WHERE id = :id');
-
- this._removeItem = createStatement(this._DBConn,
- 'DELETE FROM feed_content where id = :id');
- this._insertItem = createStatement(this._DBConn,
- 'INSERT INTO feed_content (id, feed, content, excerpt) ' +
- 'VALUES (:id, :feed, :content, :excerpt)');
-
- this._removeFeed = createStatement(this._DBConn,
- 'DELETE FROM feed_content where feed = :feed');
- }
-
- FeedStorage.prototype = {
- deleteItem: function FS_deleteItem(id) {
- this._removeItem.params.id = id;
- this._removeItem.step();
- this._removeItem.reset();
- },
- saveItem: function FS_saveItem(id, feed, content, excerpt) {
- this.deleteItem(id);
-
- var pp = this._insertItem.params;
- pp.id = id;
- pp.feed = feed;
- pp.content = content;
- pp.excerpt = excerpt;
- this._insertItem.step();
- this._insertItem.reset();
- },
- deleteFeed: function FS_deleteFeed(feed) {
- this._removeFeed.params.feed = feed;
- this._removeFeed.step();
- this._removeFeed.reset();
- },
- getContent: function FS_getContent(id) {
- return this._getData(id, this._queryContent, 'content');
- },
- getExcerpt: function FS_getExcerpt(id) {
- return this._getData(id, this._queryExcerpt, 'excerpt');
- },
- beginTransaction: function FS_beginTransation() {
- this._DBConn.beginTransaction();
- },
- commitTransaction: function FS_commitTransation() {
- this._DBConn.commitTransaction();
- },
- rollbackTransaction: function FS_rollbackTransation() {
- this._DBConn.rollbackTransaction();
- },
- _getData: function FS__getData(id, stmt, field) {
- stmt.reset();
- stmt.params.id = id;
-
- var data = null;
- if (stmt.step())
- data = stmt.row[field];
- stmt.reset();
- return data;
- }
- }
-
- function Feed(feedNode, context, coop) {
- this._feedNode = feedNode;
- this._context = context;
- this._coop = coop;
- }
-
- Feed.prototype = {
- getURL: function FEED_getURL() {
- return makeURI(this._feedNode.URL);
- },
- getFinalURL: function FEED_getFinalURL() {
- return makeURI(this._feedNode.finalURL);
- },
- getLink: function FEED_getLink() {
- return makeURI(this._feedNode.link);
- },
- getType: function FEED_getType() {
- return this._feedNode.format;
- },
- getTitle: function FEED_getTitle() {
- return checkEmpty(this._feedNode.name);
- },
- getSubtitle: function FEED_getSubtitle() {
- return checkEmpty(this._feedNode.subtitle);
- },
- getImage: function FEED_getImage() {
- return makeURI(this._feedNode.image);
- },
- getFavicon: function FEED_getFavicon() {
- return makeURI(this._feedNode.favicon);
- },
- getAuthor: function FEED_getAuthor() {
- return checkEmpty(this._feedNode.author);
- },
- getPubDate: function FEED_getPubDate() {
- return this._feedNode.datevalue;
- },
- getItemCount: function FEED_getItemCount() {
- return this._feedNode.count;
- },
- getItem: function FEED_getItem(index) {
- var items = this.getItems();
- var i = 0;
- while (items.hasMoreElements()) {
- var item = items.getNext();
- if (i == index)
- return item;
- i++;
- }
- return null;
- },
- getItems: function FEED_getItems() {
- var filter = function(feed, item, coop) {
- return new FeedItem(feed, item, coop);
- };
- return this._enumerateItems(filter);
- },
- refresh: function FEED_refresh() {
- this._feedNode.nextRefresh = new Date();
- },
- getContext: function FEED_getContext() {
- return this._context;
- },
- getUnreadCount: function FEED_getUnreadCount() {
- return this._feedNode.unseenItems;
- },
- getUnreadItems: function FEED_getUnreadItems() {
- var filter = function(feed, item, coop) {
- if (item.unseen)
- return new FeedItem(feed, item, coop);
- };
- return this._enumerateItems(filter);
- },
- getFlaggedItems: function FEED_getFlaggedItems() {
- var filter = function(feed, item, coop) {
- if (item.flagged)
- return new FeedItem(feed, item, coop);
- };
- return this._enumerateItems(filter);
- },
- setTitle: function FEED_setTitle(title) {
- this._feedNode.name = title;
- },
- markRead: function FEED_markRead() {
- var items = this._feedNode.children.enumerate();
- while (items.hasMoreElements()) {
- var item = items.getNext();
- item.unseen = false;
- }
- var countsPropagator = Cc['@flock.com/stream-counts-propagator;1']
- .getService(Ci.flockIStreamCountsPropagator);
- countsPropagator.syncCounts(this._feedNode.resource());
- },
- getFolder: function FEED_getFolder() {
- if (this._context) {
- var parents = this._feedNode.getParents();
- if (parents.length == 1) {
- return new FeedFolder(parents[0], this._context, this._coop);
- } else if (parents.length > 1) {
- var id = this._context._contextNode.id();
- for each (var parent in parents) {
- var node = parent;
- while (!node.isInstanceOf(this._coop.FeedContext)) {
- var nodeParents = node.getParents();
- if (nodeParents.length)
- node = nodeParents[0];
- else
- break;
- }
- if (node.isInstanceOf(this._coop.FeedContext) && node.id() == id)
- return new FeedFolder(parent, this._context, this._coop);
- }
- }
- }
-
- return null;
- },
- id: function FEED_id() {
- return this._feedNode.id();
- },
-
- _enumerateItems: function FEED__enumerateItems(filterFunc) {
- var feed = this;
- var coop = this._coop;
- var items = this._feedNode.children.enumerate();
-
- var filteredEnumerator = {
- next: null,
- hasMoreElements: function() {
- while (items.hasMoreElements()) {
- this.next = filterFunc(feed, items.getNext(), coop);
- if (this.next)
- return true;
- }
- return false;
- },
- getNext: function() {
- return this.next;
- },
- }
-
- return filteredEnumerator;
- },
-
- getInterfaces: function FEED_getInterfaces(countRef) {
- var interfaces = [Ci.flockIFeed, Ci.flockIFeedFolderItem,
- Ci.flockICoopObject, Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function FEED_getHelperForLanguage(language) {
- return null;
- },
- contractID: FEED_CONTRACTID,
- classDescription: FEED_CLASSNAME,
- classID: FEED_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
-
- QueryInterface: function FEED_QueryInterface(iid) {
- if (iid.equals(Ci.flockIFeed) ||
- iid.equals(Ci.flockIFeedFolderItem) ||
- iid.equals(Ci.flockICoopObject) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function FeedItem(feed, itemNode, coop) {
- this._feed = feed;
- this._itemNode = itemNode;
- this._coop = coop;
- }
-
- FeedItem.prototype = {
- getItemID: function FI_getItemID() {
- return checkEmpty(this._itemNode.itemid);
- },
- getLink: function FI_getLink() {
- return makeURI(this._itemNode.URL);
- },
- getTitle: function FI_getTitle() {
- return checkEmpty(this._itemNode.name);
- },
- getPubDate: function FI_getPubDate() {
- return this._itemNode.pubdate;
- },
- getCorrectedPubDate: function FI_getCorrectedPubDate() {
- return this._itemNode.datevalue;
- },
- getAuthor: function FI_getAuthor() {
- return checkEmpty(this._itemNode.author);
- },
- getContent: function FI_getContent() {
- return gFeedStorage.getContent(this._itemNode.id());
- },
- getExcerpt: function FI_getExcerpt() {
- return gFeedStorage.getExcerpt(this._itemNode.id());
- },
- getContext: function FI_getContext() {
- },
- isRead: function FI_isRead() {
- return !this._itemNode.unseen;
- },
- isFlagged: function FI_isFlagged() {
- return this._itemNode.flagged;
- },
- setRead: function FI_setRead(read) {
- this._itemNode.unseen = !read;
- },
- setFlagged: function FI_setFlagged(flagged) {
- if (this._itemNode.flagged == flagged)
- return;
-
- this._itemNode.flagged = flagged;
-
- var feedNode = this._feed._feedNode;
- var contexts = getContextsForFeed(feedNode, this._coop);
-
- if (flagged) {
- for each (var context in contexts) {
- context.flaggedItems.addItem(this._itemNode);
- }
- } else {
- for each (var context in contexts) {
- context.flaggedItems.children.remove(this._itemNode);
- }
- }
- },
- getFeed: function FI_getFeed() {
- return this._feed;
- },
-
- getInterfaces: function FI_getInterfaces(countRef) {
- var interfaces = [Ci.flockIFeedItem, Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function FI_getHelperForLanguage(language) {
- return null;
- },
- contractID: ITEM_CONTRACTID,
- classDescription: ITEM_CLASSNAME,
- classID: ITEM_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
-
- QueryInterface: function FI_QueryInterface(iid) {
- if (iid.equals(Ci.flockIFeedItem) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function FeedFolder(folderNode, context, coop) {
- this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
- this._logger.init('feedmanager');
- this._logger.info('created folder object');
-
- this._folderNode = folderNode;
- this._context = context;
- this._coop = coop;
- }
-
- FeedFolder.prototype = {
- getTitle: function FF_getTitle() {
- return this._folderNode.name;
- },
- getItemCount: function FF_getItemCount() {
- },
- getItems: function FF_getItems() {
- },
- setTitle: function FF_setTitle(title) {
- this._folderNode.name = title;
- },
- getUnreadCount: function FF_getUnreadCount() {
- return this._folderNode.unseenItems;
- },
- markRead: function FF_markRead() {
- var children = this.getChildren();
- while (children.hasMoreElements()) {
- var child = children.getNext().QueryInterface(Ci.flockIFeedFolderItem);
- child.markRead();
- }
- },
- subscribeURL: function FF_subscribeURL(url, title) {
- var feedObj = { URL: url, serviceId: FM_CONTRACTID };
-
- if (title) {
- title = title.replace(/[\r\n]+/g, ' ');
- this._logger.info('subscribing feed ' + url + ' with title ' + title);
- feedObj.name = title;
- } else {
- this._logger.info('subscribing feed ' + url);
- }
-
- var feedNode = new this._coop.Feed(feedObj);
-
- if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
- setFeedIndexable(feedNode, true);
-
- this._folderNode.children.addOnce(feedNode);
-
- if (feedNode.nextRefresh)
- updateNextRefresh(feedNode, this._coop);
- else
- feedNode.nextRefresh = new Date();
-
- feedNode.isPollable = true;
-
- var feed = new Feed(feedNode, null, this._coop);
- this._context.notifyOnSubscribe(feed);
-
- return feed;
- },
- subscribeFeed: function FF_subscribeFeed(feed) {
- var feedNode = this._subscriptionNode(feed, 'subscribing');
-
- if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
- setFeedIndexable(feedNode, true);
-
- this._folderNode.children.addOnce(feedNode);
-
- updateNextRefresh(feedNode, this._coop);
- feedNode.isPollable = true;
-
- this._context.notifyOnSubscribe(feed);
- },
- unsubscribeFeed: function FF_unsubscribeFeed(feed) {
- var feedNode = this._subscriptionNode(feed, 'unsubscribing');
-
- this._folderNode.children.remove(feedNode);
-
- var contexts = getContextsForFeed(feedNode, this._coop);
- if (contexts.length == 0)
- feedNode.isPollable = false;
-
- if (contexts.length == 0 ||
- !contextHasObject(NEWS_CONTEXT_NAME, feedNode, this._coop))
- setFeedIndexable(feedNode, false);
-
- this._context.notifyOnUnsubscribe(feed);
- },
- subscribeFeedWithPosition:
- function FF_subscribeFeedWithPosition(feed, target, orientation) {
- var coop = this._coop;
- var feedNode = this._subscriptionNode(feed, 'subscribing');
-
- var check, value;
- if (target instanceof Ci.flockIFeedFolder) {
- value = target.getTitle();
- check = function(child) {
- return child.isInstanceOf(coop.FeedFolder) && child.name == value;
- }
- } else {
- if (orientation == Ci.flockIFeedFolder.ORIENT_INSIDE)
- throw Cr.NS_ERROR_INVALID_ARG;
-
- value = target.getURL().spec;
- check = function(child) {
- return child.isInstanceOf(coop.Feed) && child.URL == value;
- }
- }
-
- var children = this._folderNode.children.enumerate();
- while (children.hasMoreElements()) {
- var node = children.getNext();
- if (check(node)) {
- switch (orientation) {
- case Ci.flockIFeedFolder.ORIENT_INSIDE:
- node.children.addOnce(feedNode);
- break;
- case Ci.flockIFeedFolder.ORIENT_ABOVE:
- var container = this._folderNode.children;
- container.insertAt(feedNode, container.indexOf(node));
- break;
- case Ci.flockIFeedFolder.ORIENT_BELOW:
- var container = this._folderNode.children;
- container.insertAt(feedNode, container.indexOf(node) + 1);
- break;
- default:
- throw Cr.NS_ERROR_INVALID_ARG;
- break;
- }
-
- updateNextRefresh(feedNode, this._coop);
- feedNode.isPollable = true;
-
- if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
- setFeedIndexable(feedNode, true);
-
- this._context.notifyOnSubscribe(feed);
- return;
- }
- }
-
- throw Components.Exception("Couldn't find feed");
- },
- addFolder: function FF_addFolder(title) {
- this._logger.info('adding folder ' + title);
- var children = this._folderNode.children.enumerate();
- while (children.hasMoreElements()) {
- var child = children.getNext();
- if (child.name == title) {
- var msg = 'Folder already exists: ' + title;
- this._logger.error(msg);
- throw Components.Exception(msg);
- }
- }
- var folder = new this._coop.FeedFolder({ name: title });
- this._folderNode.children.add(folder);
- return new FeedFolder(folder, this._context, this._coop);
- },
- removeFolder: function FF_removeFolder(folder) {
- var name = folder.getTitle();
- this._logger.info('removing folder ' + name);
- var children = this._folderNode.children.enumerate();
- while (children.hasMoreElements()) {
- var child = children.getNext();
- if (child.name == name && child.isInstanceOf(this._coop.FeedFolder)) {
- var childFolder = new FeedFolder(child, this._context, this._coop);
- childFolder._destroySelf();
- return;
- }
- }
- this._logger.warn('folder ' + name + ' not found');
- },
- getChildFolder: function FF_getChildFolder(title) {
- var children = this._folderNode.children.enumerate();
- while (children.hasMoreElements()) {
- var obj = children.getNext();
- if (obj.isInstanceOf(this._coop.FeedFolder) && obj.name == title)
- return new FeedFolder(obj, this._context, this._coop);
- }
- throw Components.Exception("Folder does not exist: " + title);
- },
- getChildren: function FF_getChildren() {
- var coop = this._coop;
- var context = this._context;
-
- var children = this._folderNode.children.enumerate();
-
- var enumerator = {
- hasMoreElements: function() {
- return children.hasMoreElements();
- },
- getNext: function() {
- var obj = children.getNext();
- if (obj.isInstanceOf(coop.FeedFolder))
- return new FeedFolder(obj, context, coop)
- else
- return new Feed(obj, context, coop)
- }
- };
-
- return enumerator;
- },
- getFolder: function FF_getFolder() {
- if (this._folderNode.isInstanceOf(this._coop.FeedContext))
- return null;
-
- var parents = this._folderNode.getParents();
- if (parents.length > 0)
- return new FeedFolder(parents[0], this._context, this._coop);
-
- return null;
- },
-
- _subscriptionNode: function FF___subscriptionNode(feed, action) {
- var url = feed.getURL().spec;
- this._logger.info(action + ' feed ' + url + ' from folder ' +
- this.getTitle());
-
- var feedNode = getFeedNode(url, this._coop);
- if (!feedNode)
- throw Cr.NS_ERROR_FAILURE;
-
- return feedNode;
- },
-
- _destroySelf: function FF__destroySelf() {
- var feeds = [];
- var folders = [];
-
- var children = this._folderNode.children.enumerateBackwards();
- while (children.hasMoreElements()) {
- var child = children.getNext();
- if (child.isInstanceOf(this._coop.FeedFolder)) {
- var folder = new FeedFolder(child, this._context, this._coop);
- folders.push(folder);
- } else if (child.isInstanceOf(this._coop.Feed)) {
- var feed = new Feed(child, this._context, this._coop);
- feeds.push(feed);
- }
- }
-
- for each (var folder in folders)
- folder._destroySelf();
-
- for each (var feed in feeds)
- this.unsubscribeFeed(feed);
-
- var name = this._folderNode.name;
- this._folderNode.destroy();
-
- this._logger.info('folder ' + name + ' removed');
- },
-
- getInterfaces: function FF_getInterfaces(countRef) {
- var interfaces = [Ci.flockIFeedFolder, Ci.flockIFeedFolderItem,
- Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function FF_getHelperForLanguage(language) {
- return null;
- },
- contractID: FF_CONTRACTID,
- classDescription: FF_CLASSNAME,
- classID: FF_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
-
- QueryInterface: function FF_QueryInterface(iid) {
- if (iid.equals(Ci.flockIFeedFolder) ||
- iid.equals(Ci.flockIFeedFolderItem) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
- function FeedContext(contextNode, coop) {
- this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
- this._logger.init('feedmanager');
- this._logger.info('created context for ' + contextNode.name);
-
- this._coop = coop;
- this._contextNode = contextNode;
-
- this._observers = [];
- }
-
- FeedContext.prototype = {
- getName: function FC_getName() {
- return this._contextNode.name;
- },
-
- getRoot: function FC_getRoot() {
- return new FeedFolder(this._contextNode, this, this._coop);
- },
-
- getSubscription: function FC_getSubscription(url) {
- if (!this.existsSubscription(url))
- return null;
-
- var feedNode = getFeedNode(url.spec, this._coop);
- return new Feed(feedNode, this, this._coop);
- },
- getSubscriptions: function FC_getSubscriptions() {
- var context = this;
- var coop = this._coop;
- var feeds = this._coop.Feed.all();
-
- var filteredEnumerator = {
- next: null,
- hasMoreElements: function() {
- while (feeds.hasMoreElements()) {
- var feedNode = feeds.getNext();
- if (contextHasObject(context._contextNode.name, feedNode, coop)) {
- this.next = new Feed(feedNode, context, coop);
- return true;
- }
- }
- return false;
- },
- getNext: function() {
- return this.next;
- }
- }
-
- return filteredEnumerator;
- },
- existsSubscription: function FC_existsSubscription(url) {
- var feedNode = getFeedNode(url.spec, this._coop);
- if (!feedNode)
- return false;
-
- return contextHasObject(this._contextNode.name, feedNode, this._coop);
- },
-
- refresh: function FC_refresh() {
- var feeds = this.getSubscriptions();
- while (feeds.hasMoreElements()) {
- var feed = feeds.getNext().QueryInterface(Ci.flockIFeed);
- feed.refresh();
- }
- },
-
- getRefreshInterval: function FC_getRefreshInterval() {
- return this._contextNode.refreshInterval / 60;
- },
- setRefreshInterval: function FC_setRefreshInterval(minutes) {
- this._contextNode.refreshInterval = minutes * 60;
- },
- getFeedItemCap: function FC_getFeedItemCap() {
- },
- setFeedItemCap: function FC_setFeedItemCap(maxItems) {
- },
-
- addObserver: function FC_addObserver(observer) {
- function hasFunc(element, index, array) {
- return element == observer;
- }
- if (!this._observers.some(hasFunc)) {
- this._observers.push(observer);
- if (this._observers.length == 1)
- this._watchUnseenItems();
- }
- },
- removeObserver: function FC_removeObserver(observer) {
- function keepFunc(element, index, array) {
- return element != observer;
- }
- this._observers = this._observers.filter(keepFunc);
- if (this._observers.length == 0)
- this._unwatchUnseenItems();
- },
-
- _watchUnseenItems: function FC__watchUnseenItems() {
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
- var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
-
- var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
- .getService(Ci.flockIRDFObservable);
- faves.addArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
- this._contextNode.resource(), unseenItems,
- null, this);
- },
- _unwatchUnseenItems: function FC__unwatchUnseenItems() {
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
- var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
-
- var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
- .getService(Ci.flockIRDFObservable);
- faves.removeArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
- this._contextNode.resource(), unseenItems,
- null, this);
- },
- rdfChanged: function FC_rdfChanged(ds, type, rsrc, pred, obj, oldObj) {
- for each (var observer in this._observers) {
- observer.onUnreadCountChange(this, this._contextNode.unseenItems);
- }
- },
-
- notifyOnSubscribe: function FC__notifyOnSubscribe(feed) {
- for each (var observer in this._observers) {
- observer.onSubscribe(this, feed);
- }
- },
- notifyOnUnsubscribe: function FC__notifyOnUnsubscribe(feed) {
- for each (var observer in this._observers) {
- observer.onUnsubscribe(this, feed);
- }
- },
-
- getInterfaces: function FC_getInterfaces(countRef) {
- var interfaces = [Ci.flockIFeedContext, Ci.flockIRDFObserver,
- Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function FC_getHelperForLanguage(language) {
- return null;
- },
- contractID: FC_CONTRACTID,
- classDescription: FC_CLASSNAME,
- classID: FC_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
-
- QueryInterface: function FC_QueryInterface(iid) {
- if (iid.equals(Ci.flockIFeedContext) ||
- iid.equals(Ci.flockIRDFObserver) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function FeedRequest(fm, url, coop, listener, use_feed_api, metadataOnly) {
- this._fm = fm;
- this._url = url;
- this._coop = coop;
- this._listener = listener;
- this._use_feed_api = use_feed_api;
- this._metadataOnly = metadataOnly;
-
- this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
- this._logger.init('feedmanager');
-
- this._channel = gIOService.newChannelFromURI(url);
-
- try {
- this._httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
- }
- catch (e) {
- this._httpChannel = null;
- }
- }
-
- FeedRequest.prototype = {
- get: function FR_get() {
- if (this._httpChannel) {
- var feedNode = getFeedNode(this._url.spec, this._coop);
- if (feedNode && feedNode.lastModification)
- this._httpChannel.setRequestHeader('If-Modified-Since',
- feedNode.lastModification,
- false);
- }
-
- this._channel.asyncOpen(this, null);
- },
-
- onStartRequest: function FR_onStartRequest(request, context) {
- var channel = request.QueryInterface(Ci.nsIChannel);
-
- if (Components.isSuccessCode(request.status))
- channel.contentType = 'text/xml';
-
- this._processor = Cc['@mozilla.org/feed-processor;1']
- .createInstance(Ci.nsIFeedProcessor);
- this._processor.listener = this;
-
- if (this._metadataOnly) {
- this._processor.QueryInterface(Ci.flockIFeedProcessor);
- this._processor.parseFeedMetadataAsync(null, channel.URI);
- } else {
- this._processor.parseAsync(null, channel.URI);
- }
-
- this._processor.onStartRequest(request, context);
- },
-
- onStopRequest: function FR_onStopRequest(request, context, status) {
- if (this._processor) {
- this._processor.onStopRequest(request, context, status);
- this._processor = null;
- }
- },
-
- onDataAvailable: function FR_onDataAvailable(request, context, inputStream,
- sourceOffset, count) {
- if (this._processor)
- this._processor.onDataAvailable(request, context, inputStream,
- sourceOffset, count);
- },
-
- handleResult: function FR_handleResult(result) {
- var failed = true;
-
- if (result && result.doc) {
- var feed = this._fm.storeFeed(this._url, result);
- if (feed) {
- failed = false;
-
- if (this._httpChannel && !this._metadataOnly)
- this._saveLastModification(feed._feedNode);
-
- this._notifyListener(feed);
- }
- } else if (this._httpChannel &&
- Components.isSuccessCode(this._httpChannel.status) &&
- this._httpChannel.responseStatus == 304) {
- this._logger.info('Feed not modified: ' + this._url.spec);
-
- var feedNode = getFeedNode(this._url.spec, this._coop);
- if (feedNode) {
- updateNextRefresh(feedNode, this._coop);
-
- if (feedNode.state != 'failed') {
- feedNode.lastFetch = new Date();
-
- var feed = new Feed(feedNode, null, this._coop);
- this._notifyListener(feed);
- }
- }
- else if (this._listener)
- this._listener.onError(null);
-
- failed = false;
- }
-
- if (failed) {
- this._logger.warn('Error processing feed: ' + this._url.spec);
-
- if (!this._metadataOnly) {
- var feedNode = getFeedNode(this._url.spec, this._coop);
- if (feedNode) {
- feedNode.state = 'failed';
- updateNextRefresh(feedNode, this._coop);
-
- if (this._httpChannel)
- this._saveLastModification(feedNode);
- }
- }
-
- if (this._listener)
- this._listener.onError(null);
- }
-
- this._processor = null;
- },
-
- _notifyListener: function FR__notifyListener(feed) {
- if (this._listener) {
- if (this._use_feed_api)
- this._listener.onGetFeedComplete(feed);
- else
- this._listener.onResult();
- }
- },
- _saveLastModification: function FR__saveLastModification(feedNode) {
- var lastModification = null;
- try {
- lastModification = this._httpChannel.getResponseHeader('Last-Modified');
- }
- catch (e) { }
-
- if (lastModification)
- feedNode.lastModification = lastModification;
- },
-
- QueryInterface: function FR_QueryInterface(iid) {
- if (iid.equals(Ci.nsIFeedResultListener) ||
- iid.equals(Ci.nsIStreamListener) ||
- iid.equals(Ci.nsIRequestObserver)||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
- }
-
-
- function FeedManager() {
- var obs = getObserverService();
- obs.addObserver(this, 'xpcom-shutdown', false);
-
- this._start();
- }
-
- FeedManager.prototype = {
- _start: function FM__start() {
- this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);
- var evtID = this._profiler.profileEventStart("feedmgr-init");
-
- this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
- this._logger.init('feedmanager');
- this._logger.info('starting up...');
-
- this._obsService = getObserverService();
-
- gIOService = Cc['@mozilla.org/network/io-service;1']
- .getService(Ci.nsIIOService);
-
- var sbs = Cc['@mozilla.org/intl/stringbundle;1']
- .getService(Ci.nsIStringBundleService);
- var bundle = sbs.createBundle(URI_FEED_PROPERTIES);
- gReadMoreBlurb = bundle.GetStringFromName('flock.feed.temp.readmore');
-
- this._coop = Cc['@flock.com/singleton;1'].getService(Ci.flockISingleton)
- .getSingleton('chrome://browser/content/flock/common/load-faves-coop.js')
- .wrappedJSObject;
-
- gFeedStorage = new FeedStorage();
-
- var feedroot = new this._coop.Folder(URN_FEED_ROOT);
-
- this._feedContexts = {};
- this.createFeedContext(NEWS_CONTEXT_NAME);
- this.createFeedContext(LIVEMARKS_CONTEXT_NAME);
-
- this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
-
- var prefService = Cc['@mozilla.org/preferences-service;1']
- .getService(Ci.nsIPrefBranch2);
- prefService.addObserver(PREF_EXPIRATION_TIME_BRANCH, this, false);
-
- this.observe(null, 'nsPref:changed', PREF_EXPIRATION_TIME);
- this.observe(null, 'nsPref:changed', PREF_METADATA_EXPIRATION_TIME);
-
- this._profiler.profileEventEnd(evtID, "");
- },
- _shutdown: function FM__shutdown() {
- var prefService = Cc['@mozilla.org/preferences-service;1']
- .getService(Ci.nsIPrefBranch2);
- prefService.removeObserver(PREF_EXPIRATION_TIME_BRANCH, this);
-
- this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
-
- gIOService = null;
- gFeedStorage = null;
-
- this._obsService = null;
- },
- _updateExpirationTimes: function FM__updateExpirationTimes(topic, pref) {
- var prefService = Cc['@mozilla.org/preferences-service;1']
- .getService(Components.interfaces.nsIPrefService);
- var prefBranch = prefService.getBranch(PREF_EXPIRATION_TIME_BRANCH);
-
- var val = 0;
- try {
- val = prefBranch.getIntPref(pref);
- }
- catch (e) { }
-
- if (pref == PREF_EXPIRATION_TIME) {
- this._refreshExpire = val > 0 ? val : DEFAULT_EXPIRATION_TIME;
- this._refreshExpire *= 60 * 1000;
- } else {
- this._metadataExpire = val > 0 ? val : DEFAULT_METADATA_EXPIRATION_TIME;
- this._metadataExpire *= 60 * 1000;
- }
- },
-
- observe: function FM_observe(subject, topic, state) {
- var obs = getObserverService();
-
- switch (topic) {
- case 'xpcom-shutdown':
- obs.removeObserver(this, 'xpcom-shutdown');
- this._shutdown();
- break;
-
- case 'nsPref:changed':
- this._updateExpirationTimes(topic, state);
- break;
- }
- },
-
- getFeed: function FM_getFeed(url, listener) {
- this._logger.info('getting feed: ' + url.spec);
-
- var feedNode = getFeedNode(url.spec, this._coop);
- if (feedNode && !feedNode.metadataOnly) {
- var age = Date.now() - feedNode.lastFetch.getTime();
- if (age < this._refreshExpire) {
- var feed = new Feed(feedNode, null, this._coop);
- if (listener)
- listener.onGetFeedComplete(feed);
- return;
- }
- }
-
- var req = new FeedRequest(this, url, this._coop, listener, true, false);
- req.get();
- },
-
- getFeedMetadata: function FM_getFeedMetadata(url, listener) {
- this._logger.info('getting feed metatdata: ' + url.spec);
-
- var feedNode = getFeedNode(url.spec, this._coop);
- if (feedNode) {
- if (!feedNode.metadataOnly) {
- this.getFeed(url, listener);
- return;
- } else {
- var age = Date.now() - feedNode.lastFetch.getTime();
- if (age < this._metadataExpire) {
- var feed = new Feed(feedNode, null, this._coop);
- if (listener)
- listener.onGetFeedComplete(feed);
- return;
- }
- }
- }
-
- var req = new FeedRequest(this, url, this._coop, listener, true, true);
- req.get();
- },
-
- createFeedContext: function FM_createFeedContext(name) {
- var ctxt = this._feedContexts[name];
- if (ctxt)
- return ctxt;
-
- var context = new this._coop.FeedContext({ name: name });
- context.flaggedItems = new this._coop.FeedFlaggedStream({ context: context });
-
- var feedroot = this._coop.get(URN_FEED_ROOT);
- feedroot.children.addOnce(context);
-
- ctxt = new FeedContext(context, this._coop);
- this._feedContexts[name] = ctxt;
-
- return ctxt;
- },
-
- getFeedContext: function FM_getFeedContext(name) {
- var ctxt = this._feedContexts[name];
- if (ctxt)
- return ctxt;
-
- var urn = this._coop.FeedContext.get_id({ name: name });
- var context = this._coop.get(urn);
- if (context) {
- ctxt = new FeedContext(context, this._coop);
- this._feedContexts[name] = ctxt;
- return ctxt;
- } else {
- throw Components.Exception("No feed context named " + name);
- }
- },
-
- getFeedContexts: function FM_getFeedContexts() {
- var contexts = this._coop.FeedContext.all();
- var fm = this;
- var ctor = function(contextNode, coop) {
- var ctxt = fm._feedContexts[contextNode.name];
- if (!ctxt) {
- ctxt = new FeedContext(contextNode, coop);
- fm.feedContexts[contextNode.name] = ctxt;
- }
- return ctxt;
- }
- return this._enumerateObjects(contexts, ctor);
- },
-
- deleteFeedContext: function FM_deleteFeedContext(name) {
- var urn = this._coop.FeedContext.get_id({ name: name });
- var context = this._coop.get(urn);
- if (!context)
- throw Components.Exception("No feed context named " + name);
-
- delete this._feedContexts[name];
-
- context.flaggedItems.destroy();
- // FIXME: delete folders as well
- context.destroy();
- },
-
- existsFeed: function FM_existsFeed(url) {
- var urn = this._coop.Feed.get_id({ URL: url.spec });
- return this._coop.Feed.exists(urn);
- },
-
- refresh: function FM_refresh(urn, listener) {
- this._logger.info('refreshing feed: ' + urn);
- var feedNode = this._coop.get(urn);
- if (feedNode) {
- /*
- if (!feedNode.metadataOnly) {
- var age = Date.now() - feedNode.lastFetch.getTime();
- if (age < this._refreshExpire) {
- if (listener)
- listener.onResult();
- return;
- }
- }
- */
-
- var req = new FeedRequest(this, makeURI(feedNode.URL), this._coop,
- listener, false, false);
- req.get();
- } else {
- var msg = 'could not get feed: ' + urn;
- this._logger.error(msg);
- throw Components.Exception(msg, Cr.NS_ERROR_UNEXPECTED);
- }
- },
-
- getFeedFolderItem: function FM_getFeedFolderItem(urn) {
- this._logger.info('getting feed folder item: ' + urn);
- var node = this._coop.get(urn);
- if (node) {
- var context = this.getFeedContext(NEWS_CONTEXT_NAME);
- if (node.isInstanceOf(this._coop.Feed)) {
- return new Feed(node, context, this._coop);
- } else if (node.isInstanceOf(this._coop.FeedFolder)) {
- return new FeedFolder(node, context, this._coop);
- }
- }
-
- this._logger.warn('could not get feed folder item: ' + urn);
- return null;
- },
- getFeedItem: function FM_getFeedItem(urn) {
- this._logger.info('getting feed item: ' + urn);
- var node = this._coop.get(urn);
- if (node && node.isInstanceOf(this._coop.FeedItem)) {
- var parents = node.getParents();
- for each (var parent in parents) {
- if (parent.isInstanceOf(this._coop.Feed)) {
- var context = this.getFeedContext(NEWS_CONTEXT_NAME);
- var feed = new Feed(parent, context, this._coop);
- return new FeedItem(feed, node, this._coop);
- }
- }
- }
-
- this._logger.warn('could not get feed item: ' + urn);
- return null;
- },
-
- getLibrary: function FM_getLibrary() {
- var feeds = this._coop.Feed.all();
- var ctor = function(feedNode, coop) {
- return new Feed(feedNode, null, coop);
- }
- return this._enumerateObjects(feed, ctor);
- },
-
- storeFeed: function FM_storeFeed(feedURL, feedResult) {
- if (feedResult.bozo)
- return null;
-
- this._logger.info('storing feed: ' + feedURL.spec);
-
- var feed = feedResult.doc;
- feed.QueryInterface(Ci.nsIFeed);
-
- var title = feed.title ? feed.title.plainText() : null;
- var subtitle = feed.subtitle ? feed.subtitle.plainText() : null;
-
- var format = feedResult.version ? feedResult.version : UNKNOWN_FEED_TYPE;
-
- var author = null;
- if (feed.authors && feed.authors.length)
- author = feed.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
-
- var image = null;
- try {
- image = makeURI(feed.image.getPropertyAsAString('url'));
- }
- catch (e) { }
-
- var existingFeed = getFeedNode(feedURL.spec, this._coop);
-
- var flockFeed = feed.QueryInterface(Ci.flockIFeedContainer);
- if (flockFeed.metadataOnly)
- this._coop.beginRecoverableUpdates();
- else if (existingFeed)
- title = existingFeed.name;
-
- var feedNode = this._newFeedNode(feedURL, feedResult.uri,
- title, subtitle, feed.link, author,
- format, image, flockFeed.metadataOnly);
-
- feedNode.lastFetch = new Date();
-
- if (flockFeed.metadataOnly) {
- this._coop.endRecoverableUpdates();
- return new Feed(feedNode, null, this._coop);
- }
-
- updateNextRefresh(feedNode, this._coop);
-
- insertFavicon(feedNode);
-
- var feedItems = feed.items;
- var numItems = feedItems.length;
-
- var items = [];
-
- for (var i = 0; i < numItems; i++) {
- var item = feedItems.queryElementAt(i, Ci.nsIFeedEntry);
- var datevalue = item.updated ? new Date(item.updated)
- : feedNode.datevalue;
- items.push({item: item, datevalue: datevalue, indexvalue: i});
- }
-
- items.sort(feedItemSorter);
-
- var refDate;
- var children = feedNode.children.enumerateBackwards();
- if (children.hasMoreElements())
- refDate = children.getNext().datevalue;
- else
- refDate = new Date(0);
-
- gFeedStorage.beginTransaction();
-
- try {
- var itemsAdded = 0;
- for each (var i in items) {
- var node = this._storeFeedEntry(i.item, feedNode, refDate, feed);
- if (node)
- itemsAdded++;
- }
- }
- finally {
- gFeedStorage.commitTransaction();
- }
-
- children = feedNode.children.enumerateBackwards();
- if (children.hasMoreElements())
- feedNode.datevalue = children.getNext().datevalue;
- else
- feedNode.datevalue = new Date();
-
- var feedObj = new Feed(feedNode, null, this._coop);
-
- if (itemsAdded && existingFeed)
- this._obsService.notifyObservers(feedObj, 'new-feed-items', null);
-
- return feedObj;
- },
-
- _newFeedNode: function FM__newFeedNode(feedURL, finalURL, title, subtitle,
- link, author, format, image,
- metadataOnly) {
- var feedInfo = { URL: feedURL.spec,
- finalURL: finalURL ? finalURL.spec : feedURL.spec,
- name: title ? title : feedURL.spec,
- subtitle: subtitle,
- link: link ? link.spec : feedURL.spec,
- author: author ? author : null,
- format: format ? format : UNKNOWN_FEED_TYPE,
- image: image ? image.spec : null,
- metadataOnly: metadataOnly,
- state: 'ok',
- serviceId: FM_CONTRACTID
- };
- return new this._coop.Feed(feedInfo);
- },
-
- _storeFeedEntry: function FM__storeFeedEntry(item, feedNode, refDate, feed) {
- var title = null;
- if (item.title)
- title = item.title.plainText();
-
- var author = null;
- if (item.authors && item.authors.length)
- author = item.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
-
- var pubdate = item.published;
- if (!pubdate)
- pubdate = item.updated;
- if (!pubdate)
- pubdate = feed.updated;
-
- var content = null;
- if (item.content)
- content = item.content;
- else if (item.summary)
- content = item.summary;
-
- var excerptSource = null;
- if (item.summary)
- excerptSource = item.summary.plainText();
- else if (item.content)
- excerptSource = item.content.plainText();
-
- return this._storeFeedItem(feedNode, item.id, title,
- item.link, author, pubdate, refDate,
- content, excerptSource,
- false, false);
- },
-
- _storeFeedItem: function FM__storeFeedItem(feedNode, id, title, link, author,
- pubdate, refDate,
- content, excerptSource,
- read, flagged) {
- if (!id && link)
- id = link.spec;
-
- if (!link)
- link = makeURI(feedNode.URL);
-
- if (!title && excerptSource)
- title = excerptSource.substr(0, TITLE_TRIM_MAX_CHARS) + '...';
-
- if (title)
- title = title.replace(/[\r\n]+/g, ' ');
- else
- title = link.spec;
-
- if (pubdate)
- pubdate = new Date(pubdate);
-
- var itemInfo = { itemid: id,
- URL: link.spec,
- name: title,
- author: author,
- unseen: !read,
- flagged: flagged,
- parentfeed: feedNode.URL
- };
-
- //TODO: we'll want to be more intelligent about this, but the goal should be to only update feed items that need updating
- var rdfId = this._coop.FeedItem.get_id(itemInfo);
- if (rdfId && this._coop.FeedItem.exists(rdfId))
- return null;
-
- itemInfo.pubdate = pubdate;
-
- if (!pubdate)
- itemInfo.datevalue = new Date();
- else if (pubdate.getTime() > Date.now())
- itemInfo.datevalue = new Date();
- else if (pubdate < refDate)
- itemInfo.datevalue = new Date();
- else
- itemInfo.datevalue = pubdate;
-
- itemInfo.isIndexable = feedNode.isIndexable;
-
- this._logger.info('storing feeditem: ' + itemInfo.name);
-
- var itemNode = new this._coop.FeedItem(itemInfo);
-
- if (feedNode.children.indexOf(itemNode) < 0)
- feedNode.addItem(itemNode);
-
- if (content) {
- if (typeof(content) != 'string') {
- var parser = Cc['@mozilla.org/xmlextras/domparser;1']
- .createInstance(Ci.nsIDOMParser);
- var doc = parser.parseFromString('<div/>', 'application/xhtml+xml');
-
- var docElem = doc.documentElement;
- var fragment = content.createDocumentFragment(docElem);
- docElem.appendChild(fragment);
-
- this._filterFeedItemContent(doc, content.base);
-
- var serializer = Cc['@mozilla.org/xmlextras/xmlserializer;1']
- .createInstance(Ci.nsIDOMSerializer);
- var data = serializer.serializeToString(doc);
- } else {
- data = content;
- }
-
- var excerpt = excerptText(excerptSource, itemInfo.URL);
- gFeedStorage.saveItem(itemNode.id(), feedNode.URL, data, excerpt);
- } else {
- gFeedStorage.deleteItem(itemNode.id());
- }
-
- return itemNode;
- },
- _filterFeedItemContent: function FM__filterFeedItemContent(doc, baseURI) {
- if (!baseURI)
- return;
-
- var tw = doc.createTreeWalker(doc.documentElement,
- Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
- null, false);
- node = tw.nextNode();
- while (node) {
- for each (var attrName in ATTR_URI_LIST) {
- var attr = node.attributes.getNamedItem(attrName);
- if (attr) {
- var nval = attr.nodeValue;
- try {
- var newURI = gIOService.newURI(nval, null, baseURI);
- attr.nodeValue = newURI.spec;
- }
- catch (e) { }
- }
- }
- node = tw.nextNode();
- }
- },
-
- _deleteItemContent: function FM__deleteItemContent(item, coop) {
- gFeedStorage.deleteItem(item.id());
- },
-
- _purgeFeed: function FM__purgeFeed(feed) {
- this._logger.info('Purging ' + feed.id());
-
- var items = feed.children.enumerateBackwards();
- if (items.hasMoreElements()) {
- this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
-
- gFeedStorage.deleteFeed(feed.URL);
-
- while (items.hasMoreElements())
- items.getNext().destroy();
-
- this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
- }
-
- feed.destroy();
- },
-
- _enumerateObjects: function FM__enumerateObjects(objects, ctor) {
- var coop = this._coop;
-
- var enumerator = {
- hasMoreElements: function() {
- return objects.hasMoreElements();
- },
- getNext: function() {
- return ctor(objects.getNext(), coop);
- }
- }
-
- return enumerator;
- },
-
- _purgeOrphanedFeeds: function FM__purgeOrphanedFeeds() {
- var feeds = this._coop.Feed.all();
-
- var fm = this;
-
- var purge = {
- notify: function(timer) {
- var start = Date.now();
- while (feeds.hasMoreElements()) {
- var feed = feeds.getNext();
-
- var contexts = getContextsForFeed(feed, fm._coop);
- if (contexts.length == 0)
- fm._purgeFeed(feed);
-
- if (Date.now() > start + PURGE_RUN_INTERVAL)
- return;
- }
-
- timer.cancel();
- }
- };
-
- var timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
- timer.initWithCallback(purge, PURGE_SLEEP_INTERVAL,
- Ci.nsITimer.TYPE_REPEATING_SLACK);
-
- },
-
- /* flockIHousekeeping interface */
- runHousekeeping: function FM_runHousekeeping() {
- this._logger.info('running housekeeping...');
- this._purgeOrphanedFeeds();
- },
-
- /* flockIMigratable interface */
- get shortname() { return 'News Feeds'; },
-
- needsMigration: function FM_needsMigration(oldVersion) {
- var oldFeedsFile = this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE);
- return oldFeedsFile.exists();
- },
- startMigration: function FM_startMigration(oldVersion, listener) {
- var obs = getObserverService();
- obs.notifyObservers(null, 'myworld-enableDisable-observers', false);
-
- this._start();
-
- gFeedStorage.beginTransaction();
-
- var ctxt = {
- listener: listener,
-
- oldFeedsFile : this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE),
- oldFlaggedFile : this._getOldFeedServiceFile(OLD_FLAGGED_RDF_FILE),
- oldRootFile : this._getOldFeedServiceFile(OLD_ROOT_RDF_FILE),
- oldDiscoveryFile : this._getOldFeedServiceFile(OLD_DISCOVERY_RDF_FILE),
- oldFeedDir : this._getOldFeedServiceFile(OLD_FEED_DATA_DIR),
-
- oldFeedsReported : false,
- oldFlaggedReported : false,
- cleanupReported : false,
- };
-
- return { wrappedJSObject: ctxt };
- },
- finishMigration: function FM_finishMigration(ctxtWrapper) {
- gFeedStorage.commitTransaction();
-
- var obs = getObserverService();
- obs.notifyObservers(null, 'myworld-enableDisable-observers', true);
- },
- doMigrationWork: function FM_doMigrationWork(ctxtWrapper) {
- var ctxt = ctxtWrapper.wrappedJSObject;
-
- if (!ctxt.oldFeedsReported) {
- ctxt.listener.onUpdate(0, 'Migrating subscriptions');
- ctxt.oldFeedsReported = true;
- } else if (ctxt.oldFeedsFile.exists()) {
- if (!ctxt.oldFeedsMigrator)
- ctxt.oldFeedsMigrator = this._migrateOldFeeds(ctxt);
- if (ctxt.oldFeedsMigrator.next())
- ctxt.oldFeedsMigrator = null;
- } else if (!ctxt.oldFlaggedReported) {
- ctxt.listener.onUpdate(-1, 'Migrating saved articles');
- ctxt.oldFlaggedReported = true;
- } else if (ctxt.oldFlaggedFile.exists()) {
- this._migrateOldFlagged(ctxt);
- } else if (!ctxt.cleanupReported) {
- ctxt.listener.onUpdate(-1, 'Cleaning up old data');
- ctxt.cleanupReported = true;
- } else if (ctxt.oldRootFile.exists()) {
- ctxt.oldRootFile.remove(false);
- } else if (ctxt.oldDiscoveryFile.exists()) {
- ctxt.oldDiscoveryFile.remove(false);
- } else if (ctxt.oldFeedDir.exists()) {
- ctxt.oldFeedDir.remove(true);
- return false;
- } else {
- return false;
- }
-
- return true;
- },
-
- _getOldFeedServiceFile: function FM__getOldFeedServiceFile(filename) {
- var oldFile = Cc['@mozilla.org/file/directory_service;1']
- .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
- oldFile.append(filename)
- return oldFile;
- },
- _migrateOldFeeds: function FM__migrateOldFeeds(ctxt) {
- this._hex_md5 = loadSubScript('chrome://browser/content/flock/common/md5.js').hex_md5;
-
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
- var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
- .getService(Ci.nsIRDFContainerUtils);
-
- var spec = gIOService.newFileURI(ctxt.oldFeedsFile).spec;
- var ds = RDFS.GetDataSourceBlocking(spec);
- var root = RDFS.GetResource(SUBSCRIPTIONS_RDF_ROOT);
-
- var context = this.getFeedContext(NEWS_CONTEXT_NAME);
- var news_root = context.getRoot();
-
- var titleProp = RDFS.GetResource(FLOCK_NS + 'title');
-
- var typeProp = RDFS.GetResource(FLOCK_NS + 'type');
- var feeds = ds.GetSources(typeProp, RDFS.GetLiteral('feed'), true);
-
- var numFeeds = 0;
- while (feeds && feeds.hasMoreElements()) {
- var feed = feeds.getNext();
- numFeeds++;
- }
-
- var children = RDFCU.MakeSeq(ds, root).GetElements(), i = 0;
- while (children && children.hasMoreElements()) {
- var child = children.getNext();
- child.QueryInterface(Ci.nsIRDFResource);
-
- if (RDFCU.IsContainer(ds, child)) {
- var title = ds.GetTarget(child, titleProp, true);
- if (!title)
- continue;
-
- title.QueryInterface(Ci.nsIRDFLiteral);
- if (!title.Value)
- continue;
-
- var subFolder = news_root.addFolder(title.Value);
-
- var subChildren = RDFCU.MakeSeq(ds, child).GetElements();
- while (subChildren && subChildren.hasMoreElements()) {
- var subChild = subChildren.getNext();
- subChild.QueryInterface(Ci.nsIRDFResource);
-
- var url = subChild.Value;
- var percent = Math.round(i / numFeeds * 100);
- ctxt.listener.onUpdate(percent, 'Migrating ' + url);
- yield false;
-
- i++;
- this._migrateFeed(url, subFolder);
- }
- } else {
- var url = child.Value;
- var percent = Math.round(i / numFeeds * 100);
- ctxt.listener.onUpdate(percent, 'Migrating ' + url);
- yield false;
-
- i++;
- this._migrateFeed(url, news_root);
- }
- }
-
- var oldFile = ctxt.oldFeedsFile.clone();
- oldFile.moveTo(null, OLD_FEEDS_RDF_FILE_RELIC);
- //ctxt.oldFeedsFile.remove(false);
-
- yield true;
- },
- _migrateFeed: function FM__migrateFeed(url, folder) {
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
- var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
- .getService(Ci.nsIRDFContainerUtils);
-
- var oldFeedFile = this._getOldFeedServiceFile(OLD_FEED_DATA_DIR);
- oldFeedFile.append(this._hex_md5(url));
- oldFeedFile.append(OLD_FEED_DATA_FILENAME);
-
- if (!oldFeedFile.exists())
- return;
-
- var spec = gIOService.newFileURI(oldFeedFile).spec;
- var ds = RDFS.GetDataSourceBlocking(spec);
- var root = RDFS.GetResource(OLD_FEED_DATA_RDF_PREFIX + url);
-
- function getTarget(prop) {
- var t = ds.GetTarget(root, prop, true);
- return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
- }
-
- var title = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
- var subtitle = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
- var author = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
- var link = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
- var format = getTarget(RDFS.GetResource(FLOCK_NS + 'feedtype'));
- var image = getTarget(RDFS.GetResource(FLOCK_NS + 'image'));
- var favicon = getTarget(RDFS.GetResource(FLOCK_NS + 'favicon'));
-
- if (FORMAT_CONVERSIONS[format])
- format = FORMAT_CONVERSIONS[format];
- else if (format.indexOf('RSS') == 0)
- format = 'rssUnknown';
- else
- format = null;
-
- var feedURL = makeURI(url);
- if (!feedURL)
- return;
-
- var feedNode = this._newFeedNode(feedURL, null, title, subtitle,
- makeURI(link), author, format,
- makeURI(image), false);
-
- var faviconURL = makeURI(favicon);
- if (faviconURL)
- feedNode.favicon = faviconURL.spec;
-
- folder.subscribeFeed(new Feed(feedNode, null, this._coop));
-
- var items = [];
-
- var children = RDFCU.MakeSeq(ds, root).GetElements();
- while (children && children.hasMoreElements()) {
- var item = this._migrateFeedItem(ds, children.getNext(), false);
- if (item)
- items.push(item);
- }
-
- items.sort(feedItemSorter);
-
- for each (var i in items) {
- var args = i.item;
- args.unshift(feedNode);
- this._storeFeedItem.apply(this, args);
- }
-
- children = feedNode.children.enumerateBackwards();
- if (children.hasMoreElements())
- feedNode.datevalue = children.getNext().datevalue;
- else
- feedNode.datevalue = new Date();
- },
- _migrateOldFlagged: function FM__migrateOldFlagged(ctxt) {
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
- var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
- .getService(Ci.nsIRDFContainerUtils);
-
- var spec = gIOService.newFileURI(ctxt.oldFlaggedFile).spec;
- var ds = RDFS.GetDataSourceBlocking(spec);
- var root = RDFS.GetResource(FLAGGED_RDF_ROOT);
-
- var context = new this._coop.FeedContext({ name: NEWS_CONTEXT_NAME });
- var flaggedItems = context.flaggedItems;
-
- var feedProp = RDFS.GetResource(FLOCK_NS + 'feed');
-
- var children = RDFCU.MakeSeq(ds, root).GetElements();
- while (children && children.hasMoreElements()) {
- var child = children.getNext();
- child.QueryInterface(Ci.nsIRDFResource);
-
- var feedURL = ds.GetTarget(child, feedProp, true);
- if (!feedURL)
- continue;
-
- feedURL.QueryInterface(Ci.nsIRDFLiteral);
-
- var feedNode = getFeedNode(feedURL.Value, this._coop);
- if (feedNode) {
- var args = this._migrateFeedItem(ds, child, true).item;
- args.unshift(feedNode);
- var node = this._storeFeedItem.apply(this, args);
- if (node)
- flaggedItems.addItem(node);
- }
- }
-
- var oldFile = ctxt.oldFlaggedFile.clone();
- oldFile.moveTo(null, OLD_FLAGGED_RDF_FILE_RELIC);
- //ctxt.oldFlaggedFile.remove(false);
- },
- _migrateFeedItem: function FM__migrateFeedItem(ds, res, doFlagged) {
- var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
- .getService(Ci.nsIRDFService);
-
- res.QueryInterface(Ci.nsIRDFResource);
-
- function getTarget(prop) {
- var t = ds.GetTarget(res, prop, true);
- return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
- }
-
- var flagged = getTarget(RDFS.GetResource(FLOCK_NS + 'flagged'));
- if (flagged == 'true' && !doFlagged)
- return;
-
- var id = res.Value;
- if (id.indexOf(OLD_FEED_DATA_POST_PREFIX) == 0)
- id = id.substr(OLD_FEED_DATA_POST_PREFIX.length);
- else
- id = null;
-
- var title = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
- var link = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
- var datevalue = getTarget(RDFS.GetResource(FLOCK_NS + 'datevalue'));
- var author = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
- var content = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
- var read = getTarget(RDFS.GetResource(FLOCK_NS + 'read'));
-
- datevalue = new Date(Number(datevalue));
- refDate = new Date(0);
-
- var item = [id, title, makeURI(link), author, datevalue, refDate, content,
- content, read == 'true', doFlagged];
-
- return ({ item: item, datevalue: datevalue, indexvalue: 0 });
- },
-
- getInterfaces: function FM_getInterfaces(countRef) {
- var interfaces = [Ci.flockIFeedManager, Ci.flockIPollingService,
- Ci.flockIMigratable, Ci.flockIHousekeeping,
- Ci.nsIObserver, Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function FM_getHelperForLanguage(language) {
- return null;
- },
- contractID: FM_CONTRACTID,
- classDescription: FM_CLASSNAME,
- classID: FM_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
- flags: Ci.nsIClassInfo.SINGLETON,
-
- QueryInterface: function FM_QueryInterface(iid) {
- if (iid.equals(Ci.flockIFeedManager) ||
- iid.equals(Ci.flockIPollingService) ||
- iid.equals(Ci.flockIMigratable) ||
- iid.equals(Ci.flockIHousekeeping) ||
- iid.equals(Ci.nsIObserver) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function GenericComponentFactory(ctor) {
- this._ctor = ctor;
- }
-
- GenericComponentFactory.prototype = {
-
- _ctor: null,
-
- // nsIFactory
- createInstance: function(outer, iid) {
- if (outer != null)
- throw Cr.NS_ERROR_NO_AGGREGATION;
- return (new this._ctor()).QueryInterface(iid);
- },
-
- // nsISupports
- QueryInterface: function(iid) {
- if (iid.equals(Ci.nsIFactory) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
- };
-
- var Module = {
- QueryInterface: function(iid) {
- if (iid.equals(Ci.nsIModule) ||
- iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- getClassObject: function(cm, cid, iid) {
- if (!iid.equals(Ci.nsIFactory))
- throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-
- if (cid.equals(FEED_CLASSID))
- return new GenericComponentFactory(Feed);
- if (cid.equals(ITEM_CLASSID))
- return new GenericComponentFactory(FeedItem);
- if (cid.equals(FC_CLASSID))
- return new GenericComponentFactory(FeedContext);
- if (cid.equals(FF_CLASSID))
- return new GenericComponentFactory(FeedFolder);
- if (cid.equals(FM_CLASSID))
- return new GenericComponentFactory(FeedManager);
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- registerSelf: function(cm, file, location, type) {
- var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
- cr.registerFactoryLocation(FEED_CLASSID, FEED_CLASSNAME, FEED_CONTRACTID,
- file, location, type);
- cr.registerFactoryLocation(ITEM_CLASSID, ITEM_CLASSNAME, ITEM_CONTRACTID,
- file, location, type);
- cr.registerFactoryLocation(FC_CLASSID, FC_CLASSNAME, FC_CONTRACTID,
- file, location, type);
- cr.registerFactoryLocation(FF_CLASSID, FF_CLASSNAME, FF_CONTRACTID,
- file, location, type);
- cr.registerFactoryLocation(FM_CLASSID, FM_CLASSNAME, FM_CONTRACTID,
- file, location, type);
-
- var catman = Cc['@mozilla.org/categorymanager;1']
- .getService(Ci.nsICategoryManager);
-
- catman.addCategoryEntry('flockMigratable', FM_CLASSNAME, FM_CONTRACTID,
- true, true);
- catman.addCategoryEntry('flockHousekeeping', FM_CLASSNAME, FM_CONTRACTID,
- true, true);
- },
-
- unregisterSelf: function(cm, location, type) {
- var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
- cr.unregisterFactoryLocation(FEED_CLASSID, location);
- cr.unregisterFactoryLocation(ITEM_CLASSID, location);
- cr.unregisterFactoryLocation(FC_CLASSID, location);
- cr.unregisterFactoryLocation(FF_CLASSID, location);
- cr.unregisterFactoryLocation(FM_CLASSID, location);
- },
-
- canUnload: function(cm) {
- return true;
- },
- };
-
- function NSGetModule(compMgr, fileSpec)
- {
- return Module;
- }
-
-
- function loadSubScript(spec) {
- var loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
- .getService(Ci.mozIJSSubScriptLoader);
- var context = {};
- loader.loadSubScript(spec, context);
- return context;
- }
-